/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


#include "r_local.h"


/*
 ==================
 R_CullBounds

 Returns cullBits, or CULL_OUT if completely outside the frustum
 ==================
*/
int R_CullBounds (const odBounds &bounds, int planeBits){

	odPlane	*plane;
	int		side, cullBits;
	int		i;

	cullBits = 0;

	// Check against frustum planes
	for (i = 0, plane = tr.viewParms.frustum; i < 5; i++, plane++){
		if (!(planeBits & BIT(i)))
			continue;

		side = plane->BoundsOnSide(bounds);

		if (side == PLANESIDE_BACK){
			tr.pc.cullOut++;
			return CULL_OUT;
		}

		if (side == PLANESIDE_CROSS)
			cullBits |= BIT(i);
	}

	if (cullBits)
		tr.pc.cullClip++;
	else
		tr.pc.cullIn++;

	return cullBits;
}

/*
 ==================
 R_CullEntityBounds

 Returns cullBits, or CULL_OUT if completely outside the frustum
 ==================
*/
int R_CullEntityBounds (const odBounds &bounds, int planeBits, entity_t *entity){
 
	odPlane	*plane;
	odVec3	corners[8], tmp;
	int		side, cullBits;
	int		i, j;

	cullBits = 0;

	// Compute the corner points of the bounding volume
	for (i = 0; i < 8; i++){
		tmp[0] = (i & 1) ? bounds[0][0] : bounds[1][0];
		tmp[1] = (i & 2) ? bounds[0][1] : bounds[1][1];
		tmp[2] = (i & 4) ? bounds[0][2] : bounds[1][2];

		// Transform into world space
		entity->e.axis.UnprojectVector(tmp, corners[i]);
		corners[i] += entity->e.origin;
	}

	// Check against frustum planes
	for (i = 0, plane = tr.viewParms.frustum; i < 5; i++, plane++){
		if (!(planeBits & BIT(i)))
			continue;

		side = 0;

		for (j = 0; j < 8; j++){
			side |= plane->PointOnSide(corners[j]);

			if (side == PLANESIDE_CROSS)
				break;
		}

		if (side == PLANESIDE_BACK){
			tr.pc.cullOut++;
			return CULL_OUT;
		}

		if (side == PLANESIDE_CROSS)
			cullBits |= BIT(i);
	}

	if (cullBits)
		tr.pc.cullClip++;
	else
		tr.pc.cullIn++;

	return cullBits;
}

/*
 ==================
 R_CullSphere

 Returns cullBits, or CULL_OUT if completely outside the frustum
 ==================
*/
int R_CullSphere (const odVec3 &center, float radius, int planeBits){

	odPlane	*plane;
	int		side, cullBits;
	int		i;

	cullBits = 0;

	// Check against frustum planes
	for (i = 0, plane = tr.viewParms.frustum; i < 5; i++, plane++){
		if (!(planeBits & BIT(i)))
			continue;

		side = plane->SphereOnSide(center, radius);

		if (side == PLANESIDE_BACK){
			tr.pc.cullOut++;
			return CULL_OUT;
		}

		if (side == PLANESIDE_CROSS)
			cullBits |= BIT(i);
	}

	if (cullBits)
		tr.pc.cullClip++;
	else
		tr.pc.cullIn++;

	return cullBits;
}

/*
 ==================
 R_CullEntitySphere

 Returns cullBits, or CULL_OUT if completely outside the frustum
 ==================
*/
int R_CullEntitySphere (const odVec3 &center, float radius, int planeBits, entity_t *entity){

	odPlane	*plane;
	odVec3	point;
	int		side, cullBits;
	int		i;

	cullBits = 0;

	// Transform into world space
	entity->e.axis.UnprojectVector(center, point);
	point += entity->e.origin;

	// Check against frustum planes
	for (i = 0, plane = tr.viewParms.frustum; i < 5; i++, plane++){
		if (!(planeBits & BIT(i)))
			continue;

		side = plane->SphereOnSide(point, radius);

		if (side == PLANESIDE_BACK){
			tr.pc.cullOut++;
			return CULL_OUT;
		}

		if (side == PLANESIDE_CROSS)
			cullBits |= BIT(i);
	}

	if (cullBits)
		tr.pc.cullClip++;
	else
		tr.pc.cullIn++;

	return cullBits;
}


// ============================================================================


/*
 ==================
 R_AddMDLModel
 ==================
*/
void R_AddMDLModel (entity_t *entity){

	mdlModel_t		*model = (mdlModel_t *)entity->e.model->data;
	mdlSurface_t	*surface;
	material_t		*material;
	int				cullBits;
	int				i;

	// Cull bounds
	if (!r_skipCulling->integerValue && !entity->e.depthHack){
		cullBits = R_CullEntityBounds(model->bounds, 31, entity);

		if (cullBits == CULL_OUT)
			return;
	}
	else
		cullBits = 0;

	// Mark as visible for this view
	entity->viewCount = tr.viewCount;

	tr.pc.entities++;

	// Perform a callback if needed
	if (entity->e.callback){
		entity->e.callback(&entity->e);

		entity->e.callback = NULL;
	}

	// If it has a single surface, no need to cull again
	if (model->numSurfaces == 1)
		cullBits = 0;

	// Add all the surfaces
	for (i = 0, surface = model->surfaces; i < model->numSurfaces; i++, surface++){
		// Get the material
		if (entity->e.material)
			material = entity->e.material;
		else
			material = surface->material;

		if (!material->numStages)
			continue;		// Don't bother drawing

		// Cull bounds
		if (cullBits){
			if (R_CullEntityBounds(surface->bounds, cullBits, entity) == CULL_OUT)
				continue;
		}

		// Set up subview if needed
		if (material->subviewType != ST_NONE)
			R_SetupSubview(material, entity, surface->facePlanes);

		// Add the draw mesh
		R_AddDrawMesh(MESH_MODEL, surface, material, entity);
	}
}

/*
void R_AddMD5Model (entity_t *entity){

	mdlModel_t		*model;
	mdlSurface_t	*surface;
	material_t		*material;
	int				cullBits;
	int				i;

	// Cull bounds
	if (!r_skipCulling->integerValue && !entity->e.depthHack){
		cullBits = R_CullEntityBounds(entity->e.bounds, 31, entity);

		if (cullBits == CULL_OUT)
			return;
	}
	else
		cullBits = 0;

	// Mark as visible for this view
	entity->viewCount = tr.viewCount;

	tr.pc.entities++;

	// Perform a callback if needed
	if (entity->e.callback){
		entity->e.callback(&entity->e);

		entity->e.callback = NULL;
	}

	// Instantiate
	model = R_InstantiateDynamicModel(entity, true, false);

	// If it has a single surface, no need to cull again
	if (model->numSurfaces == 1)
		cullBits = 0;

	// Add all the surfaces
	for (i = 0, surface = model->surfaces; i < model->numSurfaces; i++, surface++){
		// Get the material
		if (entity->e.material)
			material = entity->e.material;
		else
			material = surface->material;

		if (!material->numStages)
			continue;		// Don't bother drawing

		// Cull bounds
		if (cullBits){
			if (R_CullEntityBounds(surface->bounds, cullBits, entity) == CULL_OUT)
				continue;
		}

		// Set up subview if needed
		if (material->subviewType != ST_NONE)
			R_SetupSubview(material, entity, surface->facePlanes);

		// Add the draw mesh
		R_AddDrawMesh(MESH_MODEL, surface, material, entity);
	}
}
*/
/*
 ==================
 R_AddSprite
 ==================
*/
void R_AddSprite (entity_t *entity){

	material_t	*material;

	// Get the material
	if (entity->e.material)
		material = entity->e.material;
	else
		material = tr.defaultMaterial;

	if (!material->numStages)
		return;		// Don't bother drawing

	// Cull sphere
	if (!r_skipCulling->integerValue && !entity->e.depthHack){
		if (R_CullSphere(entity->e.origin, entity->e.spriteRadius, 31) == CULL_OUT)
			return;
	}

	// Mark as visible for this view
	entity->viewCount = tr.viewCount;

	tr.pc.entities++;

	// Perform a callback if needed
	if (entity->e.callback){
		entity->e.callback(&entity->e);

		entity->e.callback = NULL;
	}

	// Add the draw mesh
	R_AddDrawMesh(MESH_SPRITE, NULL, material, entity);
}

/*
 ==================
 R_AddBeam
 ==================
*/
void R_AddBeam (entity_t *entity){

	material_t	*material;
	odBounds	bounds;

	// Get the material
	if (entity->e.material)
		material = entity->e.material;
	else
		material = tr.defaultMaterial;

	if (!material->numStages)
		return;		// Don't bother drawing

	// Cull bounds
	if (!r_skipCulling->integerValue && !entity->e.depthHack){
		bounds.Clear();
		bounds.AddPoint(entity->e.origin);
		bounds.AddPoint(entity->e.beamEnd);
		bounds.Expand(entity->e.beamWidth * 0.5f);

		if (R_CullBounds(bounds, 31) == CULL_OUT)
			return;
	}

	// Mark as visible for this view
	entity->viewCount = tr.viewCount;

	tr.pc.entities++;

	// Perform a callback if needed
	if (entity->e.callback){
		entity->e.callback(&entity->e);

		entity->e.callback = NULL;
	}

	// Add the draw mesh
	R_AddDrawMesh(MESH_BEAM, NULL, material, entity);
}

/*
 ==================
 R_AddEntities
 ==================
*/
void R_AddEntities (void){

	entity_t	*entity;
	model_t		*model;

	if (r_skipEntities->integerValue)
		return;

	for (entity = tr.scene->activeEntity.next; entity != &tr.scene->activeEntity; entity = entity->next){
		if (entity == tr.scene->worldEntity)
			continue;

		// Development tool
		if (r_singleEntity->integerValue >= 0){
			if (r_singleEntity->integerValue != entity->index)
				continue;
		}

		// Check for view suppression
		if (!r_skipSuppress->integerValue){
			if (!(entity->e.allowInView & tr.viewParms.viewType))
				continue;
		}

		// Add the entity
		switch (entity->e.type){
		case RE_MODEL:
			model = entity->e.model;

			if (!model || model->type == MODEL_BAD){
				Com_Printf(S_COLOR_YELLOW "WARNING: entity with no model at (%s)\n", entity->e.origin.ToString());
				break;
			}

			switch (model->type){
			case MODEL_INLINE:
				R_AddInlineModel(entity);
				break;
			case MODEL_MDL:
				R_AddMDLModel (entity);
				break;
			//case MODEL_MD5:
			//	R_AddMD5Model(entity);
			//	break;
			default:
				Com_Error(false, "R_AddEntities: bad model type (%i)", model->type);
			}

			break;
		case RE_SPRITE:
			R_AddSprite(entity);
			break;
		case RE_BEAM:
			R_AddBeam(entity);
			break;
		default:
			Com_Error(false, "R_AddEntities: bad type (%i)", entity->e.type);
		}
	}
}

/*
 ==================
 R_AddParticles
 ==================
*/
void R_AddParticles (void){

	particle_t	*particle;
	material_t	*material;

	if (r_skipParticles->integerValue)
		return;

	for (particle = tr.scene->activeParticle.next; particle != &tr.scene->activeParticle; particle = particle->next){
		// Development tool
		if (r_singleParticle->integerValue >= 0){
			if (r_singleParticle->integerValue != particle->index)
				continue;
		}

		// Check for view suppression
		if (!r_skipSuppress->integerValue){
			if (!(particle->p.allowInView & tr.viewParms.viewType))
				continue;
		}

		// Get the material
		if (particle->p.material)
			material = particle->p.material;
		else
			material = tr.defaultMaterial;

		if (!material->numStages)
			continue;		// Don't bother drawing

		// Mark as visible for this view
		particle->viewCount = tr.viewCount;

		tr.pc.particles++;

		// Add the draw mesh
		R_AddDrawMesh(MESH_PARTICLE, particle, material, tr.scene->worldEntity);
	}
}


/*
 ==============================================================================

 VIEW RENDERING

 ==============================================================================
*/


/*
 ==================
 R_SetupSubview
 ==================
*/
void R_SetupSubview (material_t *material, entity_t *entity, odPlane *plane){

	odVec3	planeNormal;
	float	planeDist;
	odMat3	axis;
	odVec3	origin, tmp;
	float	dist, d1, d2;
	int		i;

	// Portal skies are special
	if (material->subviewType == ST_PORTALSKY){
		if (tr.viewParms.viewType == VIEW_PORTALSKY){
			Com_DPrintf(S_COLOR_YELLOW "R_SetupSubview: recursive portal skies\n");
			return;
		}

		if (tr.viewParms.portalSkyVisible){
			Com_DPrintf(S_COLOR_YELLOW "R_SetupSubview: multiple portal skies\n");
			return;
		}

		tr.viewParms.portalSkyVisible = true;
		tr.viewParms.portalSkyMaterial = material;
		tr.viewParms.portalSkyEntity = entity;

		return;
	}

	// Handle a generic subview
	if (tr.viewParms.viewType != VIEW_MAIN){
		Com_DPrintf(S_COLOR_YELLOW "R_SetupSubview: recursive subviews\n");
		return;
	}

	if (tr.subviewParms.viewType){
		Com_DPrintf(S_COLOR_YELLOW "R_SetupSubview: multiple subviews\n");
		return;
	}

	tr.subviewParms.material = material;
	tr.subviewParms.entity = entity;

	switch (material->subviewType){
	case ST_MIRROR:
		// Set viewType
		tr.subviewParms.viewType = VIEW_MIRROR;

		// Copy renderView
		tr.subviewParms.renderView = tr.renderView;

		// Compute the world space mirror plane
		if (entity == tr.scene->worldEntity){
			planeNormal = plane->normal;
			planeDist = plane->dist;
		}
		else {
			entity->e.axis.UnprojectVector(plane->normal, planeNormal);
			planeDist = plane->dist + entity->e.origin.Dot(planeNormal);
		}

		// Compute mirror view origin and axis
		dist = 2.0f * (tr.renderView.origin.Dot(planeNormal) - planeDist);
		tr.subviewParms.renderView.origin = tr.renderView.origin - planeNormal * dist;

		tmp.Reflect(tr.renderView.axis[0], planeNormal);
		tr.subviewParms.renderView.axis = tmp.ToMat3();

		// Set the PVS origin
		d1 = tr.renderView.origin.Dot(planeNormal) - planeDist;
		d2 = tr.subviewParms.renderView.origin.Dot(planeNormal) - planeDist;

		if (d1 == d2)
			tr.subviewParms.pvsOrigin = tr.renderView.origin;
		else
			tr.subviewParms.pvsOrigin.Lerp(tr.renderView.origin, tr.subviewParms.renderView.origin, d1 / (d1 - d2));

		// Move it slightly off the plane to make sure we get a valid PVS
		// sample point
		tr.subviewParms.pvsOrigin += planeNormal * ON_EPSILON;

		// Set the clip plane in eye space
		tr.subviewParms.clipPlane[0] = -tr.subviewParms.renderView.axis[1].Dot(planeNormal);
		tr.subviewParms.clipPlane[1] = tr.subviewParms.renderView.axis[2].Dot(planeNormal);
		tr.subviewParms.clipPlane[2] = -tr.subviewParms.renderView.axis[0].Dot(planeNormal);
		tr.subviewParms.clipPlane[3] = tr.subviewParms.renderView.origin.Dot(planeNormal) - planeDist;

		break;
	case ST_PORTAL:
		if (!entity->e.renderView){
			Com_Error(false, "R_SetupSubview: NULL renderView for subview");
			return;
		}

		// Set viewType
		tr.subviewParms.viewType = VIEW_PORTAL;

		// Copy renderView
		tr.subviewParms.renderView = *entity->e.renderView;

		// Compute the world space portal plane
		if (entity == tr.scene->worldEntity){
			planeNormal = plane->normal;
			planeDist = plane->dist;
		}
		else {
			entity->e.axis.UnprojectVector(plane->normal, planeNormal);
			planeDist = plane->dist + entity->e.origin.Dot(planeNormal);
		}

		// Project the portal origin onto the plane to get an origin we can
		// rotate around
		dist = entity->e.origin.Dot(planeNormal) - planeDist;
		origin = entity->e.origin - planeNormal * dist;

		// Compute the portal plane axis
		axis = planeNormal.ToMat3();

		axis[0] = -axis[0];
		axis[1] = -axis[1];

		// Compute portal view origin and axis
		axis.ProjectVector(tr.renderView.origin - origin, origin);

		entity->e.renderView->axis.UnprojectVector(origin, tr.subviewParms.renderView.origin);
		tr.subviewParms.renderView.origin += entity->e.renderView->origin;

		for (i = 0; i < 3; i++){
			axis.ProjectVector(tr.renderView.axis[i], tmp);
			entity->e.renderView->axis.UnprojectVector(tmp, tr.subviewParms.renderView.axis[i]);
		}

		// Set the PVS origin
		tr.subviewParms.pvsOrigin = entity->e.renderView->origin;

		// Set the clip plane in eye space
		tr.subviewParms.clipPlane[0] = -tr.subviewParms.renderView.axis[1].Dot(entity->e.renderView->axis[0]);
		tr.subviewParms.clipPlane[1] = tr.subviewParms.renderView.axis[2].Dot(entity->e.renderView->axis[0]);
		tr.subviewParms.clipPlane[2] = -tr.subviewParms.renderView.axis[0].Dot(entity->e.renderView->axis[0]);
		tr.subviewParms.clipPlane[3] = tr.subviewParms.renderView.origin.Dot(entity->e.renderView->axis[0]) - entity->e.renderView->origin.Dot(entity->e.renderView->axis[0]);

		break;
	case ST_REMOTE:
		if (!entity->e.renderView){
			Com_Error(false, "R_SetupSubview: NULL renderView for subview");
			return;
		}

		// Set viewType
		tr.subviewParms.viewType = VIEW_REMOTE;

		// Copy renderView
		tr.subviewParms.renderView = *entity->e.renderView;

		// Set the PVS origin
		tr.subviewParms.pvsOrigin = entity->e.renderView->origin;

		break;
	default:
		Com_Error(false, "R_SetupSubview: bad subviewType (%i)", material->subviewType);
	}
}

/*
 ==================
 R_SetupViewport
 ==================
*/
static void R_SetupViewport (void){

	tr.viewParms.viewportX = M_FtolFast(tr.renderView.x * tr.renderCrops[tr.currentRenderCrop].xScale);
	tr.viewParms.viewportY = M_FtolFast(tr.renderView.y * tr.renderCrops[tr.currentRenderCrop].yScale);
	tr.viewParms.viewportWidth = M_FtolFast(tr.renderView.width * tr.renderCrops[tr.currentRenderCrop].xScale);
	tr.viewParms.viewportHeight = M_FtolFast(tr.renderView.height * tr.renderCrops[tr.currentRenderCrop].yScale);

	tr.viewParms.viewportX &= ~1;
	tr.viewParms.viewportY &= ~1;
	tr.viewParms.viewportWidth &= ~1;
	tr.viewParms.viewportHeight &= ~1;
}

/*
 ==================
 R_SetupMatrices
 ==================
*/
static void R_SetupMatrices (void){

	odVec4	corner;
	float	scale;

	// Compute the projection matrix
	if (r_zNear->floatValue < 0.1f)
		CVar_SetFloat(r_zNear, 3.0f);
	if (r_zFar->floatValue != 0.0f && r_zFar->floatValue <= r_zNear->floatValue)
		CVar_SetFloat(r_zFar, 0.0f);

	tr.viewParms.projectionMatrix[0][0] = 1.0f / M_Tan(tr.renderView.fovX * (M_PI / 360.0f));
	tr.viewParms.projectionMatrix[0][1] = 0.0f;
	tr.viewParms.projectionMatrix[0][2] = 0.0f;
	tr.viewParms.projectionMatrix[0][3] = 0.0f;
	tr.viewParms.projectionMatrix[1][0] = 0.0f;
	tr.viewParms.projectionMatrix[1][1] = 1.0f / M_Tan(tr.renderView.fovY * (M_PI / 360.0f));
	tr.viewParms.projectionMatrix[1][2] = 0.0f;
	tr.viewParms.projectionMatrix[1][3] = 0.0f;
	tr.viewParms.projectionMatrix[2][0] = 0.0f;
	tr.viewParms.projectionMatrix[2][1] = 0.0f;
	tr.viewParms.projectionMatrix[2][2] = -0.999f;
	tr.viewParms.projectionMatrix[2][3] = -1.0f;
	tr.viewParms.projectionMatrix[3][0] = 0.0f;
	tr.viewParms.projectionMatrix[3][1] = 0.0f;
	tr.viewParms.projectionMatrix[3][2] = -2.0f * r_zNear->floatValue;
	tr.viewParms.projectionMatrix[3][3] = 0.0f;

	if (r_zFar->floatValue > r_zNear->floatValue){
		scale = 1.0f / (r_zFar->floatValue - r_zNear->floatValue);

		tr.viewParms.projectionMatrix[2][2] = -(r_zFar->floatValue + r_zNear->floatValue) * scale;
		tr.viewParms.projectionMatrix[3][2] = -2.0f * r_zFar->floatValue * r_zNear->floatValue * scale;
	}

	// Shear the near plane if needed
	if (tr.viewParms.viewType == VIEW_MIRROR || tr.viewParms.viewType == VIEW_PORTAL){
		// Calculate the clip space corner point opposite the clipping plane
		// and transform it into eye space
		corner[0] = Sign(tr.subviewParms.clipPlane[0]) / tr.viewParms.projectionMatrix[0][0];
		corner[1] = Sign(tr.subviewParms.clipPlane[1]) / tr.viewParms.projectionMatrix[1][1];
		corner[2] = -1.0f;
		corner[3] = (1.0f + tr.viewParms.projectionMatrix[2][2]) / tr.viewParms.projectionMatrix[3][2];

		// Calculate the clipping plane scale
		corner *= tr.subviewParms.clipPlane;

		scale = 2.0f / (corner[0] + corner[1] + corner[2] + corner[3]);

		// Set the oblique near plane
		tr.viewParms.projectionMatrix[0][2] = tr.subviewParms.clipPlane[0] * scale;
		tr.viewParms.projectionMatrix[1][2] = tr.subviewParms.clipPlane[1] * scale;
		tr.viewParms.projectionMatrix[2][2] = tr.subviewParms.clipPlane[2] * scale + 1.0f;
		tr.viewParms.projectionMatrix[3][2] = tr.subviewParms.clipPlane[3] * scale;
	}

	// Mirror the X axis if needed
	if (tr.viewParms.viewType == VIEW_MIRROR)
		tr.viewParms.projectionMatrix[0][0] = -tr.viewParms.projectionMatrix[0][0];

	// Compute the modelview matrix
	tr.viewParms.modelviewMatrix[0][0] = -tr.renderView.axis[1][0];
	tr.viewParms.modelviewMatrix[0][1] = tr.renderView.axis[2][0];
	tr.viewParms.modelviewMatrix[0][2] = -tr.renderView.axis[0][0];
	tr.viewParms.modelviewMatrix[0][3] = 0.0f;
	tr.viewParms.modelviewMatrix[1][0] = -tr.renderView.axis[1][1];
	tr.viewParms.modelviewMatrix[1][1] = tr.renderView.axis[2][1];
	tr.viewParms.modelviewMatrix[1][2] = -tr.renderView.axis[0][1];
	tr.viewParms.modelviewMatrix[1][3] = 0.0f;
	tr.viewParms.modelviewMatrix[2][0] = -tr.renderView.axis[1][2];
	tr.viewParms.modelviewMatrix[2][1] = tr.renderView.axis[2][2];
	tr.viewParms.modelviewMatrix[2][2] = -tr.renderView.axis[0][2];
	tr.viewParms.modelviewMatrix[2][3] = 0.0f;
	tr.viewParms.modelviewMatrix[3][0] = tr.renderView.origin.Dot(tr.renderView.axis[1]);
	tr.viewParms.modelviewMatrix[3][1] = -tr.renderView.origin.Dot(tr.renderView.axis[2]);
	tr.viewParms.modelviewMatrix[3][2] = tr.renderView.origin.Dot(tr.renderView.axis[0]);
	tr.viewParms.modelviewMatrix[3][3] = 1.0f;

	// Compute the modelview-projection matrix
	tr.viewParms.mvpMatrix = tr.viewParms.projectionMatrix * tr.viewParms.modelviewMatrix;

	// Compute the sky box texture matrix
	tr.viewParms.skyBoxMatrix[0][0] = 1.0f;
	tr.viewParms.skyBoxMatrix[0][1] = 0.0f;
	tr.viewParms.skyBoxMatrix[0][2] = 0.0f;
	tr.viewParms.skyBoxMatrix[0][3] = -tr.renderView.origin[0];
	tr.viewParms.skyBoxMatrix[1][0] = 0.0f;
	tr.viewParms.skyBoxMatrix[1][1] = 1.0f;
	tr.viewParms.skyBoxMatrix[1][2] = 0.0f;
	tr.viewParms.skyBoxMatrix[1][3] = -tr.renderView.origin[1];
	tr.viewParms.skyBoxMatrix[2][0] = 0.0f;
	tr.viewParms.skyBoxMatrix[2][1] = 0.0f;
	tr.viewParms.skyBoxMatrix[2][2] = 1.0f;
	tr.viewParms.skyBoxMatrix[2][3] = -tr.renderView.origin[2];
	tr.viewParms.skyBoxMatrix[3][0] = 0.0f;
	tr.viewParms.skyBoxMatrix[3][1] = 0.0f;
	tr.viewParms.skyBoxMatrix[3][2] = 0.0f;
	tr.viewParms.skyBoxMatrix[3][3] = 1.0f;
}

/*
 ==================
 R_SetupFrustum
 ==================
*/
static void R_SetupFrustum (void){

	odPlane	*planes = tr.viewParms.frustum;
	int		i;

	planes[0].normal[0] = tr.viewParms.mvpMatrix[0][3] + tr.viewParms.mvpMatrix[0][0];
	planes[0].normal[1] = tr.viewParms.mvpMatrix[1][3] + tr.viewParms.mvpMatrix[1][0];
	planes[0].normal[2] = tr.viewParms.mvpMatrix[2][3] + tr.viewParms.mvpMatrix[2][0];
	planes[0].dist = -(tr.viewParms.mvpMatrix[3][3] + tr.viewParms.mvpMatrix[3][0]);

	planes[1].normal[0] = tr.viewParms.mvpMatrix[0][3] - tr.viewParms.mvpMatrix[0][0];
	planes[1].normal[1] = tr.viewParms.mvpMatrix[1][3] - tr.viewParms.mvpMatrix[1][0];
	planes[1].normal[2] = tr.viewParms.mvpMatrix[2][3] - tr.viewParms.mvpMatrix[2][0];
	planes[1].dist = -(tr.viewParms.mvpMatrix[3][3] - tr.viewParms.mvpMatrix[3][0]);

	planes[2].normal[0] = tr.viewParms.mvpMatrix[0][3] + tr.viewParms.mvpMatrix[0][1];
	planes[2].normal[1] = tr.viewParms.mvpMatrix[1][3] + tr.viewParms.mvpMatrix[1][1];
	planes[2].normal[2] = tr.viewParms.mvpMatrix[2][3] + tr.viewParms.mvpMatrix[2][1];
	planes[2].dist = -(tr.viewParms.mvpMatrix[3][3] + tr.viewParms.mvpMatrix[3][1]);

	planes[3].normal[0] = tr.viewParms.mvpMatrix[0][3] - tr.viewParms.mvpMatrix[0][1];
	planes[3].normal[1] = tr.viewParms.mvpMatrix[1][3] - tr.viewParms.mvpMatrix[1][1];
	planes[3].normal[2] = tr.viewParms.mvpMatrix[2][3] - tr.viewParms.mvpMatrix[2][1];
	planes[3].dist = -(tr.viewParms.mvpMatrix[3][3] - tr.viewParms.mvpMatrix[3][1]);

	planes[4].normal[0] = tr.viewParms.mvpMatrix[0][3] + tr.viewParms.mvpMatrix[0][2];
	planes[4].normal[1] = tr.viewParms.mvpMatrix[1][3] + tr.viewParms.mvpMatrix[1][2];
	planes[4].normal[2] = tr.viewParms.mvpMatrix[2][3] + tr.viewParms.mvpMatrix[2][2];
	planes[4].dist = -(tr.viewParms.mvpMatrix[3][3] + tr.viewParms.mvpMatrix[3][2]);

	for (i = 0; i < 5; i++){
		planes[i].Normalize();
		planes[i].SetType();
		planes[i].SetSignBits();
	}
}

/*
 ==================
 R_RenderPortalSky
 ==================
*/
static bool R_RenderPortalSky (void){

	viewParms_t		viewParms;
	renderView_t	renderView;
	odMat4			entityMatrix, mvpMatrix;

	if (r_skipPortalSky->integerValue)
		return false;

	if (tr.viewParms.viewType == VIEW_PORTALSKY)
		return false;		// Avoid recursion

	if (!tr.viewParms.portalSkyVisible)
		return false;		// No portal sky available

	// Backup main view
	viewParms = tr.viewParms;
	renderView = tr.renderView;

	// Render the portal sky to a texture
	tr.viewParms.viewType = VIEW_PORTALSKY;

	tr.viewParms.portalSkyVisible = false;
	tr.viewParms.portalSkyMaterial = NULL;
	tr.viewParms.portalSkyEntity = NULL;

	tr.renderView.origin = tr.renderView.portalSkyOrigin;

	R_CropRenderSize(viewParms.portalSkyMaterial->subviewWidth, viewParms.portalSkyMaterial->subviewHeight, true);
	R_RenderView();
	R_CaptureRenderToTexture("_portalSky");
	R_UnCrop();

	// Compute the portal sky texture matrix
	if (viewParms.portalSkyEntity == tr.scene->worldEntity)
		viewParms.portalSkyMatrix = viewParms.mvpMatrix.Transpose();
	else {
		entityMatrix.Set(viewParms.portalSkyEntity->e.axis, viewParms.portalSkyEntity->e.origin);

		mvpMatrix = viewParms.projectionMatrix * (viewParms.modelviewMatrix * entityMatrix);
		viewParms.portalSkyMatrix = mvpMatrix.Transpose();
	}

	// Mirror the X axis if needed
	if (viewParms.viewType == VIEW_MIRROR)
		viewParms.portalSkyMatrix[0] = -viewParms.portalSkyMatrix[0];

	// Restore main view
	tr.viewParms = viewParms;
	tr.renderView = renderView;

	// Clear portal sky
	tr.viewParms.portalSkyVisible = false;
	tr.viewParms.portalSkyMaterial = NULL;
	tr.viewParms.portalSkyEntity = NULL;

	return true;
}

/*
 ==================
 R_RenderSubview
 ==================
*/
static bool R_RenderSubview (void){

	viewParms_t		viewParms;
	renderView_t	renderView;
	odMat4			entityMatrix, mvpMatrix;

	if (r_skipSubviews->integerValue)
		return false;

	if (tr.viewParms.viewType != VIEW_MAIN)
		return false;		// Avoid recursion

	if (!tr.subviewParms.viewType)
		return false;		// No subview available

	// Backup main view
	viewParms = tr.viewParms;
	renderView = tr.renderView;

	// Render the subview to a texture
	tr.viewParms.viewType = tr.subviewParms.viewType;

	tr.viewParms.portalSkyVisible = false;
	tr.viewParms.portalSkyMaterial = NULL;
	tr.viewParms.portalSkyEntity = NULL;

	tr.renderView = tr.subviewParms.renderView;

	R_CropRenderSize(tr.subviewParms.material->subviewWidth, tr.subviewParms.material->subviewHeight, true);
	R_RenderView();
	R_CaptureRenderToTexture("_subview");
	R_UnCrop();

	// Compute the screen texture matrix
	if (tr.subviewParms.entity == tr.scene->worldEntity)
		viewParms.screenMatrix = viewParms.mvpMatrix.Transpose();
	else {
		entityMatrix.Set(tr.subviewParms.entity->e.axis, tr.subviewParms.entity->e.origin);

		mvpMatrix = viewParms.projectionMatrix * (viewParms.modelviewMatrix * entityMatrix);
		viewParms.screenMatrix = mvpMatrix.Transpose();
	}

	// Restore main view
	tr.viewParms = viewParms;
	tr.renderView = renderView;

	// Clear subview
	memset(&tr.subviewParms, 0, sizeof(subviewParms_t));

	return true;
}

/*
 ==================
 R_RenderView

 Handles portal skies, subviews, and bloom
 ==================
*/
void R_RenderView (void){

	bool	renderBloom = false;

	// Bump view count
	tr.viewCount++;

	// Set up viewport
	R_SetupViewport();

	if (tr.viewParms.viewportWidth <= 0 || tr.viewParms.viewportHeight <= 0)
		return;

	// Set up matrices and frustum
	R_SetupMatrices();
	R_SetupFrustum();

	// Set up draw mesh lists
	tr.viewParms.drawMeshes = &tr.view.drawMeshes[tr.view.firstDrawMesh];
	tr.viewParms.drawPostProcessMeshes = &tr.view.drawPostProcessMeshes[tr.view.firstDrawPostProcessMesh];

	// Set up draw light lists
	tr.viewParms.drawLights = &tr.view.drawLights[tr.view.firstDrawLight];
	tr.viewParms.drawFogLights = &tr.view.drawFogLights[tr.view.firstDrawFogLight];

	// Add draw meshes and lights
	R_AddWorld();
	R_AddEntities();
	R_AddParticles();
	R_AddDecals();
	R_AddLights();

	// Sort draw meshes
	R_SortDrawMeshes();

	// The next view rendered in this frame will tack on after this one
	tr.view.firstDrawMesh = tr.view.numDrawMeshes;
	tr.view.firstDrawPostProcessMesh = tr.view.numDrawPostProcessMeshes;

	tr.view.firstDrawLight = tr.view.numDrawLights;
	tr.view.firstDrawFogLight = tr.view.numDrawFogLights;

	// Render a subview if needed
	if (R_RenderSubview()){
		if (r_subviewOnly->integerValue)
			return;
	}

	// Render the portal sky if needed
	if (R_RenderPortalSky()){
		if (r_portalSkyOnly->integerValue)
			return;
	}

	// Render a bloom view and composite texture if needed
	if (r_bloom->integerValue){
		R_CropRenderSize(SCREEN_WIDTH >> 2, SCREEN_HEIGHT >> 2, true);
		R_SetupViewport();

		if (tr.viewParms.viewportWidth > 0 && tr.viewParms.viewportHeight > 0){
			renderBloom = true;

			R_AddRenderViewCommand(true);
			R_AddCompositeBloomCommand();
		}

		R_UnCrop();
		R_SetupViewport();
	}

	// Add a render view command
	R_AddRenderViewCommand(false);

	// Render the bloom effect if needed
	if (renderBloom)
		R_AddRenderBloomCommand();
}

/*
 ==================
 R_ResetView
 ==================
*/
void R_ResetView (void){

	// Clear draw mesh lists
	tr.view.numDrawMeshes = tr.view.firstDrawMesh = 0;
	tr.view.numDrawPostProcessMeshes = tr.view.firstDrawPostProcessMesh = 0;

	// Clear draw light lists
	tr.view.numDrawLights = tr.view.firstDrawLight = 0;
	tr.view.numDrawFogLights = tr.view.firstDrawFogLight = 0;

	// Clear primary view
	tr.primaryViewAvailable = false;
}
